Skip to content

GDB attach 加载带调试信息的共享库符号 ​

进程运行时链接的共享库若是 stripped 版本(无调试信息),GDB attach 后就看不到库函数的源码和行号断点。解决路线有两条:手头只有零星几个带符号的库时,用 add-symbol-file 把它们附加到已加载地址上;若产品提供了配套的分离调试包,用 set debug-file-directory 让 GDB 到指定目录批量加载。本文以 libzmq 为例演示前者,并解释为什么直觉方案 solib-search-path 在这个场景下不生效——这是最常见的踩坑点;文末补充调试包的标准用法。

问题 ​

进程加载的是 /lib/aarch64-linux-gnu/libzmq.so.5(无调试信息),手头另有一份编译时保留了调试信息的 libzmq.so.5,想让 attach 上的 GDB 使用后者加载符号。

关键认知 ​

GDB attach 不能改变进程的库加载路径。 进程启动时 execve 和动态链接器已完成路径解析,/proc/<pid>/maps 中的映射不可更改。GDB 能做的只是替换符号文件的查找来源。

方案对比 ​

方案 原理 优缺点
set solib-search-path 按文件名在指定目录搜索符号文件 仅在原始路径文件不存在时回退搜索,本地文件存在时无效
set sysroot 路径前缀重定向 需要构造完整目录结构,影响所有库的查找
add-symbol-file 手动指定符号文件和加载地址 最直接,不影响其他库,推荐
临时替换原文件 替换系统目录下的 so 暴力有效,但影响全局,需恢复

推荐方案:add-symbol-file ​

步骤 ​

  1. 查看目标库的加载地址
gdb
(gdb) info sharedlibrary libzmq
From                To                  Syms Read  Shared Object Library
0x0000ffffa18124e0  0x0000ffffa186f344  Yes (*)    /lib/aarch64-linux-gnu/libzmq.so.5

起始地址 0x0000ffffa18124e0 即 .text 段地址。

  1. 加载带调试信息的符号文件
gdb
(gdb) add-symbol-file /home/data/libzmq/libzmq.so.5 0x0000ffffa18124e0
add symbol table from file "/home/data/libzmq/libzmq.so.5" at
    .text_addr = 0xffffa18124e0
(y or n) y
Reading symbols from /home/data/libzmq/libzmq.so.5...
  1. 验证符号加载成功
gdb
(gdb) info functions zmq_send
All functions matching regular expression "zmq_send":

File /home/user/workspace/libzmq/src/zmq.cpp:
356:    int zmq_send(void*, void const*, size_t, int);
383:    int zmq_send_const(void*, void const*, size_t, int);
415:    int zmq_sendiov(void*, iovec*, size_t, int);
351:    int zmq_sendmsg(void*, zmq_msg_t*, int);

能列出源码位置和函数签名,说明符号生效。

  1. 正常调试
gdb
(gdb) b zmq_send
(gdb) b zmq.cpp:356
(gdb) list zmq_send

注意事项 ​

  • info sharedlibrary 中该库仍显示 Yes (*),这是正常的——它反映的是进程原始加载的 so 的状态,不影响 add-symbol-file 附加的符号
  • 如果 info functions 找不到函数,说明指定的 so 本身也是 stripped 的,需要确认文件是否包含调试信息:
bash
readelf -S /home/data/libzmq/libzmq.so.5 | grep debug

solib-search-path 为什么无效 ​

set solib-search-path 的工作机制:

  1. GDB 取进程已加载库的文件名(如 libzmq.so.5)
  2. 先尝试原始绝对路径 /lib/aarch64-linux-gnu/libzmq.so.5
  3. 只有原始路径文件不存在时,才回退到 solib-search-path 指定的目录搜索

由于 /lib/aarch64-linux-gnu/libzmq.so.5 在本地存在,GDB 直接使用了它,solib-search-path 被跳过。

sysroot 方案(备选) ​

当需要批量替换多个库的符号时,构造 sysroot 目录结构更高效:

bash
# 构造与原始路径一致的目录结构
mkdir -p /home/data/sysroot/lib/aarch64-linux-gnu/
cp /home/data/libzmq/libzmq.so.5.2.2 /home/data/sysroot/lib/aarch64-linux-gnu/
cd /home/data/sysroot/lib/aarch64-linux-gnu/
ln -sf libzmq.so.5.2.2 libzmq.so.5
ln -sf libzmq.so.5 libzmq.so
gdb
(gdb) set sysroot /home/data/sysroot
(gdb) set solib-search-path /lib/aarch64-linux-gnu:/opt/deps/lib

solib-search-path 作为补充,让 GDB 能找到 sysroot 中未覆盖的其他库。

分离调试包:debug-file-directory 路线 ​

不少产品(包括主流 Linux 发行版的 debug 包)会把调试信息从发布二进制中剥离,另行提供一套「调试包」——目录结构与整包一致,里面只有 .debug 符号文件。这时不需要逐个 add-symbol-file,用 GDB 的 set debug-file-directory 一次性指向调试包目录即可。

符号分离后直接 gdb,会看到这样的预期现象(不是故障):

Reading symbols from ./datacenter...
(No debugging symbols found in ./datacenter)
(gdb) bt
#0  0x0000fffff79a3944 in __pthread_clockjoin_ex () from /lib/.../libpthread.so.0
#2  0x0000aaaaaaaae404 in ?? ()

自有程序与动态库的堆栈帧只剩地址(??),仅系统库的导出函数还显示名称;addr2line、objdump --syms 同样查不到符号——符号已全部移入调试包,配置加载后即可恢复。

解压调试包后,三种配置方式:

bash
# 方式一:写入 ~/.gdbinit,一次配置对所有调试会话生效
echo 'set debug-file-directory /home/data/debug-info' >> ~/.gdbinit
gdb /path/to/datacenter
gdb
# 方式二:已启动的会话内设置。注意:仅 set 不会重新加载符号,须以 file 命令重读
(gdb) set debug-file-directory /home/data/debug-info
(gdb) file /path/to/datacenter
bash
# 方式三:启动时 -ex 指定,两条 -ex 顺序不可颠倒,file 不可省略
gdb -ex "set debug-file-directory /home/data/debug-info" -ex "file ./datacenter"

方式三的顺序要求值得强调:位置参数的符号加载先于 -ex 执行,把 file 写成位置参数会导致 set 不生效,这是 -ex 用法最常见的坑。

常见问题对照:

现象 处理
堆栈全部为地址,无函数名 符号全剥离的预期行为,按上表配置调试包
有函数名但无行号(形如 Run() ()) 旧版本产物,换对应版本的调试包
set debug-file-directory 未生效 设置须在符号加载前生效;会话内设置后必须 file 重读
提示 debug information does not match 调试包与二进制版本不匹配,用 readelf -n 比对双方 Build ID 后更换

与 add-symbol-file 的分工:调试包覆盖整套二进制时用 debug-file-directory(版本须严格一致);只是零星补一两个库的符号、或版本对不上需要单独指定时,用 add-symbol-file。

精确获取 .text 段地址 ​

info sharedlibrary 显示的起始地址通常就是 .text 段地址。如需精确确认:

bash
cat /proc/<pid>/maps | grep libzmq

输出中 r-xp(可读可执行)权限的行即为 .text 段:

ffffa18124e0-ffffa186f344 r-xp 00000000 ... /lib/aarch64-linux-gnu/libzmq.so.5

要点 ​

  • attach 后无法改变进程的库加载路径,只能替换 GDB 的符号查找来源
  • 零星补符号用 add-symbol-file:一行命令,不影响其他库,不需要修改任何文件
  • 整套分离调试包用 set debug-file-directory:会话内设置后必须 file 重读才生效
  • info sharedlibrary 的 (*) 标记不影响实际调试,以 info functions 和断点设置验证为准
  • solib-search-path 在原始路径文件存在时不会生效,这是最常见的踩坑点
  • 调试包版本必须与二进制一致,readelf -n 比对 Build ID 是标准校验方法
最近更新

基于 VitePress 构建